package com.xdjd.mobile.business.util;

import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.UUID;

/**
<pre>
redis分布式锁
https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/

SET key value [EX seconds] [PX milliseconds] [NX|XX]

将字符串值 value 关联到 key 。

如果 key 已经持有其他值， SET 就覆写旧值，无视类型。

对于某个原本带有生存时间（TTL）的键来说， 当 SET 命令成功在这个键上执行时， 这个键原有的 TTL 将被清除。

可选参数

从 Redis 2.6.12 版本开始， SET 命令的行为可以通过一系列参数来修改：

EX second ：设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond ：设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX ：只在键不存在时，才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX ：只在键已经存在时，才对键进行设置操作。
</pre>
 * @author tanghc
 *
 */
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    
    private static final Long RELEASE_SUCCESS = 1L;
    
    private static NoLockProcessorImpl noLockProcessorImpl = new NoLockProcessorImpl();
    
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param expireTimeMilliseconds 超期时间，多少毫秒后这把锁自动释放
     * @param lockProcessor 获得锁成功回调
     * @return 返回lockProcessor.process()返回内容
     */
    public static Object tryLock(Jedis jedis, String lockKey, int expireTimeMilliseconds, LockProcessor lockProcessor) {
    	String requestId = UUID.randomUUID().toString();
    	return tryLock(jedis, lockKey, requestId, expireTimeMilliseconds, lockProcessor, noLockProcessorImpl);
    }
    
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTimeMilliseconds 超期时间，多少毫秒后这把锁自动释放
     * @param lockProcessor 获得锁处理业务
     * @return 返回lockProcessor.process()返回内容
     */
    public static Object tryLock(Jedis jedis, String lockKey, String requestId, int expireTimeMilliseconds, LockProcessor lockProcessor) {
    	return tryLock(jedis, lockKey, requestId, expireTimeMilliseconds, lockProcessor, noLockProcessorImpl);
    }
    
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTimeMilliseconds 超期时间，多少毫秒后这把锁自动释放
     * @param lockProcessor 获得锁处理业务
     * @param noLockProcessor 未获得锁处理
     * @return 如果得到锁，返回lockProcessor.process()返回的对象，否则返回noLockProcessor.processNoLock()返回的对象
     */
    public static Object tryLock(Jedis jedis, String lockKey, String requestId, int expireTimeMilliseconds, LockProcessor lockProcessor,NoLockProcessor noLockProcessor) {
    	boolean hasLock = tryGetDistributedLock(jedis, lockKey, requestId, expireTimeMilliseconds);
    	try {
    		if(hasLock) {
    			return lockProcessor.process();
    		} else {
    			return noLockProcessor.processNoLock();
    		}
    	} finally {
    		// 释放锁
			releaseDistributedLock(jedis, lockKey, requestId);
			try {
	            if (jedis != null) {
	            	jedis.close();
	            }
	        } catch (Exception ioe) {
	            // ignore
	        }
		}
    	
    }

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTimeMilliseconds 超期时间，多少毫秒后这把锁自动释放
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTimeMilliseconds ) {
		String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTimeMilliseconds);
		
		if (LOCK_SUCCESS.equals(result)) {
			return true;
		}
		return false;

    }

    

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
		
		if (RELEASE_SUCCESS.equals(result)) {
			return true;
		}
		return false;
    }
    
    public static interface LockProcessor {
    	/**
    	 * 得到锁之后业务处理
    	 * @return
    	 */
    	Object process();
    }
    
    public static interface NoLockProcessor {
    	/**
    	 * 未得到锁处理
    	 * @return
    	 */
    	Object processNoLock();
    }
    
    private static class NoLockProcessorImpl implements NoLockProcessor {
		@Override
		public Object processNoLock() {
			return null;
		}
    	
    }
}